//
//  SPDotExporter.m
//  sequel-pro
//
//  Created by Stuart Connolly (stuconnolly.com) on April 17, 2010.
//  Copyright (c) 2009 Stuart Connolly. All rights reserved.
//
//  Permission is hereby granted, free of charge, to any person
//  obtaining a copy of this software and associated documentation
//  files (the "Software"), to deal in the Software without
//  restriction, including without limitation the rights to use,
//  copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the
//  Software is furnished to do so, subject to the following
//  conditions:
//
//  The above copyright notice and this permission notice shall be
//  included in all copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
//  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
//  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
//  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
//  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
//  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
//  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
//  OTHER DEALINGS IN THE SOFTWARE.
//
//  More info at <https://github.com/sequelpro/sequelpro>

#import <SPMySQL/SPMySQL.h>
#import "SPDotExporter.h"
#import "SPFileHandle.h"
#import "SPTableData.h"
#import "SPExportUtilities.h"
#import "SPExportFile.h"
#import "SPExportController.h"
#import "SPFunctions.h"

@implementation SPDotExporter

@synthesize delegate;
@synthesize dotExportTables;
@synthesize dotExportCurrentTable;
@synthesize dotForceLowerTableNames;
@synthesize dotTableData;
@synthesize dotDatabaseHost;
@synthesize dotDatabaseName;
@synthesize dotDatabaseVersion;

/**
 * Initialise an instance of SPDotExporter using the supplied delegate.
 *
 * @param exportDelegate The exporter delegate
 *
 * @return The initialised instance
 */
- (instancetype)initWithDelegate:(NSObject<SPDotExporterProtocol> *)exportDelegate
{
    if ((self = [super init])) {
        SPExportDelegateConformsToProtocol(exportDelegate, @protocol(SPDotExporterProtocol));
        
        [self setDelegate:exportDelegate];
        [self setDotExportCurrentTable:nil];
    }
    
    return self;
}

- (void)exportOperation
{
    NSMutableString *metaString = [NSMutableString string];
    
    // Check that we have all the required info before starting the export
    if ((![self dotExportTables]) || (![self dotTableData]) || ([[self dotExportTables] count] == 0)) {
        return;
    }
    
    // Inform the delegate that the export process is about to begin
    [delegate performSelectorOnMainThread:@selector(dotExportProcessWillBegin:) withObject:self waitUntilDone:NO];
    
    // Mark the process as running
    [self setExportProcessIsRunning:YES];
    
    // we require utf8mb4
    [connection setEncoding:@"utf8mb4"];
    
    [metaString setString:@"// ************************************************************\n"];
    [metaString appendString:@"// Generated by: Sequel Ace\n"];
    [metaString appendFormat:@"// Version %@\n//\n", [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]];
    [metaString appendFormat:@"// %@\n// %@\n//\n", SPLOCALIZEDURL_HOMEPAGE, SPDevURL];
    [metaString appendFormat:@"// Host: %@ (MySQL %@)\n", [self dotDatabaseHost], [self dotDatabaseVersion]];
    [metaString appendFormat:@"// Database: %@\n", [self dotDatabaseName]];
    [metaString appendFormat:@"// Generation Time: %@\n", [NSDate date]];
    [metaString appendString:@"// ************************************************************\n\n"];
    
    [metaString appendString:@"digraph \"Database Structure\" {\n"];
    [metaString appendFormat:@"\tlabel = \"ER Diagram: %@\";\n", [self dotDatabaseName]];
    [metaString appendString:@"\tlabelloc = t;\n"];
    [metaString appendString:@"\tcompound = true;\n"];
    [metaString appendString:@"\tnode [ shape = record ];\n"];
    [metaString appendString:@"\tfontname = \"Helvetica\";\n"];
    [metaString appendString:@"\tranksep = 1.25;\n"];
    [metaString appendString:@"\tratio = 0.7;\n"];
    [metaString appendString:@"\trankdir = LR;\n"];
    
    // Write information to the file
    [self writeUTF8String:metaString];
    
    NSMutableArray *fkInfo = [[NSMutableArray alloc] init];
    
    // Process the tables
    for (NSUInteger i = 0; i < [[self dotExportTables] count]; i++) 
    {
        if(self.exportOutputFile.fileHandleError != nil){
            SPMainQSync(^{
                [(SPExportController*)self->delegate cancelExportForFile:self->exportOutputFile.exportFilePath];
            });
            return;
        }
        
        // Check for cancellation flag
        if ([self isCancelled]) {
            
            return;
        }
        
        NSString *tableName = [[self dotExportTables] safeObjectAtIndex:i];
        NSString *tableLinkName = [self dotForceLowerTableNames] ? [tableName lowercaseString] : tableName;
        NSDictionary *tableInfo = [[self dotTableData] informationForTable:tableName fromDatabase:nil];
        
        // Set the current table
        [self setDotExportCurrentTable:tableName];
        
        // Inform the delegate that we are about to start fetcihing data for the current table
        [[delegate onMainThread] dotExportProcessWillBeginFetchingData:self forTableWithIndex:i];
        
        NSString *hdrColor = @"#DDDDDD";
        
        if ([[tableInfo objectForKey:@"type"] isEqualToString:@"View"]) {
            hdrColor = @"#DDDDFF";
        }
        
        [metaString setString:[NSString stringWithFormat:@"\tsubgraph \"table_%@\" {\n", tableName]];
        [metaString appendString:@"\t\tnode [ shape = \"plaintext\" ];\n"];
        [metaString appendFormat:@"\t\t\"%@\" [ label=<\n", tableLinkName];
        [metaString appendString:@"\t\t\t<TABLE BORDER=\"0\" CELLSPACING=\"0\" CELLBORDER=\"1\">\n"];
        [metaString appendFormat:@"\t\t\t<TR><TD COLSPAN=\"3\" BGCOLOR=\"%@\">%@</TD></TR>\n", hdrColor, tableName];
        
        // Retrieve the column definitions for the current table
        NSArray *tableColumns = [tableInfo objectForKey:@"columns"];
        
        for (NSDictionary *aColumn in tableColumns) 
        {
            [metaString appendFormat:@"\t\t\t<TR><TD COLSPAN=\"3\" PORT=\"%@\">%@:<FONT FACE=\"Helvetica-Oblique\" POINT-SIZE=\"10\">%@</FONT></TD></TR>\n", [aColumn objectForKey:@"name"], [aColumn objectForKey:@"name"], [aColumn objectForKey:@"type"]];
        }
        
        [metaString appendString:@"\t\t\t</TABLE>>\n"];
        [metaString appendString:@"\t\t];\n"];
        [metaString appendString:@"\t}\n"];
        
        [self writeUTF8String:metaString];
        
        // Check if any relations are available for the table
        NSArray *tableConstraints = [tableInfo objectForKey:@"constraints"];
        
        if ([tableConstraints count]) {
            
            for (NSDictionary* constraint in tableConstraints) 
            {
                if(self.exportOutputFile.fileHandleError != nil){
                    SPMainQSync(^{
                        [(SPExportController*)self->delegate cancelExportForFile:self->exportOutputFile.exportFilePath];
                    });
                    return;
                }
                // Check for cancellation flag
                if ([self isCancelled]) {
                    
                    return;
                }
                
                // Get the column references. Currently the columns themselves are an array,
                // while tables are comma separated if there are more than
                // one.  Only use the first of each for the time being.
                NSArray *originColumns = [constraint objectForKey:@"columns"];
                NSArray *referenceColumns = [constraint objectForKey:@"ref_columns"];
                
                NSString *extra = @"";
                
                if ([originColumns count] > 1) {
                    extra = @" [ arrowhead=crow, arrowtail=odiamond ]";
                }
                
                [fkInfo addObject:[NSString stringWithFormat:@"%@:%@ -> %@:%@ %@", tableLinkName, [originColumns objectAtIndex:0], [constraint objectForKey:@"ref_table"], [[referenceColumns objectAtIndex:0] lowercaseString], extra]];
            }
        }
        
        // Update progress
        double progress = (i * ([self exportMaxProgress] / [[self dotExportTables] count]));
        
        [self setExportProgressValue:progress];
        [delegate performSelectorOnMainThread:@selector(dotExportProcessProgressUpdated:) withObject:self waitUntilDone:NO];
    }
    
    // Inform the delegate that we are about to start fetching relations data for the current table
    [delegate performSelectorOnMainThread:@selector(dotExportProcessWillBeginFetchingRelationsData:) withObject:self waitUntilDone:NO];
    
    [metaString setString:@"edge [ arrowhead=inv, arrowtail=normal, style=dashed, color=\"#444444\" ];\n"];
    
    // Get the relations
    for (id item in fkInfo) 
    {
        [metaString appendFormat:@"%@;\n", item];
    }
    
    [metaString appendString:@"}\n"];
    
    // Write information to the file
    [self writeUTF8String:metaString];
    
    // Write data to disk
    [[self exportOutputFile] close];
    
    // Mark the process as not running
    [self setExportProcessIsRunning:NO];
    
    // Inform the delegate that the export process is complete
    [delegate performSelectorOnMainThread:@selector(dotExportProcessComplete:) withObject:self waitUntilDone:NO];
}

#pragma mark -

- (void)dealloc
{
    delegate = nil;
    
}

@end
